/*-
 * Settings.java
 *
 * $LastChangedBy: stein $
 * $LastChangedRevision: 37 $
 * $LastChangedDate: 2006-11-30 23:06:29 +0000 (Thu, 30 Nov 2006) $
 * $HeadURL: svn://localhost/SMS/src/com/googlecode/attention/sms/pdu/UDHI.java $
 *
 * Copyright (c) 2007, Stein Gunnar Bakkeby
 * All rights reserved.
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 *     * Redistributions of source code must retain the above copyright
 *       notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above copyright
 *       notice, this list of conditions and the following disclaimer in the
 *       documentation and/or other materials provided with the distribution.
 *     * Neither the name of Stein Gunnar Bakkeby nor the names of its
 *       contributors may be used to endorse or promote products derived
 *       from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS "AS IS" AND ANY
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL THE REGENTS AND CONTRIBUTORS BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */
package com.googlecode.attention.util;

import static com.googlecode.attention.util.UtilLogStatements.*;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.MissingResourceException;
import java.util.Properties;

/**
 * Class for holding settings in form of a resource bundle. The class
 * reports errors automatically dependent on the method used to retrieve
 * settings.
 * <BR>
 * <BR>Since ResourceBundle keys are case sensitive, this object provides
 * three different idioms, namely: <F>lower-case idiom</F>, <F>upper-case
 * idiom</F>, and the <F>mixed-case idiom</F>. For the latter no changes
 * is done to the keys when retrieving information from the resource
 * bundle. In the two others the keys are changed into either upper-case
 * or lower-case. This is useful as you may define that all keys in the
 * .properties file should have a particular case. As this settings object
 * can automatically transform the keys into the right form, the programmer
 * does not need to pay attention to case when retrieving information from
 * the ResourceBundle. The settings object is using the mixed-case idiom
 * by default, meaning no change is done to the keys when retrieving data.
 * It is possible to override the case for particular .properties files by
 * adding the key &quot;CASE_IDIOM&quot; or &quot;case_idiom&quot; in the
 * file with one of the following values: mixed, lower, upper.
 * 
 * @author Stein Gunnar Bakkeby
 */
public class Settings {
  /* Field values */
  /** Indicates that exceptions should be muted / hidden. */
  public static final int MUTE_EXCEPTION  = 0;
  /** Indicates that exceptions should be printed to System.err */
  public static final int PRINT_EXCEPTION = 1;
  /** Indicates that exceptions should be thrown. */
  public static final int THROW_EXCEPTION = 2;
  /** Indicates that exceptions should be logged. */
  public static final int LOG_EXCEPTION = 3;
  
  /** The settings stored in a ResourceBundle. */
  private Properties settings;
  private int exceptionHandling;

  /**
   * Constructor, creates the settings which uses a resource bundle.
   * 
   * @param settings The resource bundle
   */
  public Settings(final Properties settings) {
    this.settings = settings;
    readOperativeSettings();
  }
  
  /**
   * Constructor, retrieves the resource bundle from an input stream.
   * 
   * @param stream The input stream to read from
   * @throws IOException If file IO error when loading
   */
  public Settings(final InputStream stream) throws IOException {
    final Properties settings = new Properties();
    settings.load(stream);
    this.settings = settings;
    readOperativeSettings();
  }
  
  /**
   * Constructor, retrieves the resource bundle from a string. Note that this
   * only works if the .properties file is within the default class path.
   * 
   * @param fileName String with the path to the resource bundle
   * @throws IOException If file IO error when loading
   */
  public Settings(final String fileName) throws IOException {
    this(new FileInputStream(new File(fileName)));
  }
  
  private void readOperativeSettings() {
    /*
     * Check for override information in bundle
     * related to exception handling.
     */
    String s = settings.getProperty("exceptions");
    if (s == null) s = "";
    
         if (s.equals("mute"))  exceptionHandling = MUTE_EXCEPTION;
    else if (s.equals("print")) exceptionHandling = PRINT_EXCEPTION;
    else if (s.equals("log"))   exceptionHandling = LOG_EXCEPTION;
    else                        exceptionHandling = THROW_EXCEPTION;
  }

  /**
   * Method for retrieving settings, System.out.println() on
   * MissingResourceExceptions.
   * 
   * @param key The key
   * @return The resource, or &quot;Error, no resource found for: &quot; + key
   */
  public String getString(final String key) {
    final String s = settings.getProperty(key);
    if (s == null) {
      handleException(new MissingResourceException(
          "Could not retrieve property", this.getClass().getName(), key));
      return key;
    }
    return s;
  }
  
  /**
   * Method for retrieving integers, System.out.println() on
   * MissingResourceExceptions and NumberFormatExceptions.
   * 
   * @param key The key
   * @return The resource, or 0 on error
   */
  public int getInteger(final String key) {
    try {
      return Integer.parseInt(getString(key));
    } catch (NumberFormatException e) {
      handleException(new MissingResourceException(e.toString(),
                                                   this.getClass().getName(),
                                                   key));
      return 0;
    }
  }
  
  /**
   * Method for retrieving boolean values, System.out.println() on
   * MissingResourceExceptions.
   * 
   * @param key The key
   * @return True if value equals &quot;true&quot;
   */
  public boolean getBoolean(String key) {
    return getString(key).equalsIgnoreCase("true")
        || getString(key).equalsIgnoreCase("on")
        || getString(key).equalsIgnoreCase("yes");
  }
  
  /* Modulator methods */
  /**
   * Method to retrieve a list of items with the format name_<i> where
   * <i> starts at 0 and increments by 1 for each entry.
   * 
   * @param key The key
   * @return The list of items retrieved
   */
  public String[] getList(final String key) {
    int size = 256;
    String[] values = new String[size]; // temp
    int count  = 0;
    for (int i = 0; true; i++) {
      values[i] = settings.getProperty(key + i);
      if (values[i] == null) {
        final String[] ret = new String[i];
        System.arraycopy(values, 0, ret, 0, i);
        return ret;
      }
      if (count + 1 >= size) {
        final int tmpSize  = size;
        final String tmpInt[] = values; 
        size *= 2;
        values = new String[size];
        for (int k = 0; k < tmpSize; k++) {
          values[k] = tmpInt[k];
        }
      }
    }
  }
  
  /**
   * This will return a list of integers, System.out.println() if
   * NumberFormatException.
   * 
   * @param key The key
   * @see #getList(String)
   * @return List of integers
   * @throws NumberFormatException If list does not contain integers
   */
  public int[] getIntegers(final String key) throws NumberFormatException {
    final String[] list = getList(key);
    final int length = list.length;
    final int[] ret = new int[length];
    for (int i = 0; i < length; i++) {
      ret[i] = Integer.parseInt(list[i]);
    }
    return ret;
  }
  
  /**
   * This will return a list of boolean values.
   * 
   * @param key The key
   * @see #getList(String)
   * @return List of booleans
   */
  public boolean[] getBooleans(final String key) {
    final String[] list = getList(key);
    final int length = list.length;
    final boolean[] ret = new boolean[length];
    for (int i = 0; i < length; i++) {
      ret[i] = list[i].equalsIgnoreCase("true")
            || list[i].equalsIgnoreCase("on")
            || list[i].equalsIgnoreCase("yes");
    }
    return ret;
  }

  /**
   * This will set the behaviour of this class when a MissingResourceException
   * occurs. 
   * 
   * @param value See static field values
   */
  public void setExceptionHandling(final int value) {
    switch (value) {
      case MUTE_EXCEPTION :
      case PRINT_EXCEPTION :
      case THROW_EXCEPTION :
        exceptionHandling = value;
        break;

      default :
        /* Ignore */
        break;
    }
  }

  /**
   * This will return the exception handling. Note that the exception
   * handling may be overridden by settings contained within the
   * resource bundle.
   * 
   * @return See static field values
   */
  public int getExceptionHandling() {
    return exceptionHandling;
  }

  
  /**
   * Handles exceptions according to the exception handling defined.
   * 
   * @param e The exception
   * @see #setExceptionHandling(int)
   * @throws MissingResourceException If defined as THROW_EXCEPTION
   */
  private void handleException(final MissingResourceException e)
                               throws MissingResourceException {
    switch (exceptionHandling) {
    case MUTE_EXCEPTION:
      /* Ignore */
      break;
      
    case PRINT_EXCEPTION:
      System.out.println(e);
      e.printStackTrace();
      break;
      
    case LOG_EXCEPTION:
      UTIL_001.log(e.getKey(), e.getClassName());
      break;
      
    default:
    case THROW_EXCEPTION:
      throw e;
    }
  }
}